﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Gonte.Reflection
{
    /// <summary>
    /// Utility class to retrieve custom attributes
    /// </summary>
    public static class CustomAttribute
    {
        /// <summary>
        /// Retrieves the custom attributes attached to the attributeType
        /// </summary>
        /// <param name="attributeType">The attributeType to get the custom attribute from</param>
        /// <param name="attributeType">The attributeType of the attribute to retrieve</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The attributes as an array of objects or null if not found</returns>
        public static Object[] GetCustomAttributes(Type type, Type attributeType, bool inherit)
        {
            Object[] attributes = null;
            if (attributeType != null)
            {
                attributes = type.GetCustomAttributes(attributeType, inherit);
            }
            else
            {
                attributes = type.GetCustomAttributes(inherit);
            }

            if (attributes == null
                || attributes.Length == 0)
            {
                return null;
            }

            return attributes;
        }

        /// <summary>
        /// Retrieves the custom attributes attached to the attributeType
        /// </summary>
        /// <param name="attributeType">The attributeType to get the custom attribute from</param>
        /// <param name="attributeType">The attributeType of the attribute to retrieve</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The attributes as an array of objects or null if not found</returns>
        public static T[] GetCustomAttributes<T>(Type type, bool inherit)
        {
            Object[] objects = GetCustomAttributes(type, typeof(T), inherit);
            if (objects == null)
            {
                return null;
            }

            T[] attributes = new T[objects.Length];
            objects.CopyTo(attributes, 0);

            return attributes;
        }

        /// <summary>
        /// Retrieves the custom attribute attached to the attributeType
        /// </summary>
        /// <param name="attributeType">The attributeType to get the custom attribute from</param>
        /// <param name="attributeType">The attributeType of the attribute to retrieve</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The attribute as an object or null if not found</returns>
        public static Object GetCustomAttribute(Type type, Type attributeType, bool inherit)
        {
            Object[] attributes = null;
            if (attributeType != null)
            {
                attributes = type.GetCustomAttributes(attributeType, inherit);
            }
            else
            {
                attributes = type.GetCustomAttributes(inherit);
            }

            if (attributes == null)
            {
                return null;
            }

            switch (attributes.Length)
            {
                case 0: return null;
                case 1: return attributes[0];
                default:
                    throw new InvalidOperationException(
                        string.Format("There are more than one custom attribute of type: {0} for type {1}",
                        attributeType,
                        type)
                        );
            }
        }

        /// <summary>
        /// Retrieves the custom attribute attached to the attributeType
        /// </summary>
        /// <typeparam name="T">The attributeType of attribute to retrieve</typeparam>
        /// <param name="attributeType">The attributeType of the attribute to retrieve</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The attribute or null if not found</returns>
        public static T GetCustomAttribute<T>(Type type, bool inherit)
        {
            return (T)GetCustomAttribute(type, typeof(T), inherit);
        }

        /// <summary>
        /// Retrieves the custom attributes for the specified propertyInfo info
        /// </summary>
        /// <param name="propertyInfo">The propertyInfo we want to retrieve the attributes from</param>
        /// <param name="attributeType">The attributeType of the attribute to retrieve</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The array of attributes that match the attribute attributeType or null if not found</returns>
        public static Object[] GetCustomAttributes(PropertyInfo property, Type attributeType, bool inherit)
        {
            if (attributeType != null)
            {
                return property.GetCustomAttributes(attributeType, inherit);
            }

            return property.GetCustomAttributes(inherit);
        }

        /// <summary>
        /// Retrieves the custom attributes for the specified propertyInfo info
        /// </summary>
        /// <typeparam name="T">The attributeType of the attribute to retrieve</typeparam>
        /// <param name="propertyInfo">The propertyInfo we want to retrieve the attributes from</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The array of attributes that match the attribute attributeType or null if not found</returns>
        public static T[] GetCustomAttributes<T>(PropertyInfo property, bool inherit)
        {
            Object[] objects = GetCustomAttributes(property, typeof(T), inherit);
            if (objects == null
                || objects.Length == 0)
            {
                return null;
            }

            T[] attributes = new T[objects.Length];
            objects.CopyTo(attributes, 0);

            return attributes;
        }

        /// <summary>
        /// Retrieves the custom attribute attached to the propertyInfo
        /// </summary>
        /// <param name="propertyInfo">The propertyInfo info to search for the attached attribute</param>
        /// <param name="attributeType">The attributeType of the attribute to retrieve</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The attribute as an object or null if not found</returns>
        public static Object GetCustomAttribute(PropertyInfo property, Type attributeType, bool inherit)
        {
            Object[] attributes = null;
            if (attributeType != null)
            {
                attributes = property.GetCustomAttributes(attributeType, inherit);
            }
            else
            {
                attributes = property.GetCustomAttributes(inherit);
            }

            if (attributes == null)
            {
                return null;
            }

            switch (attributes.Length)
            {
                case 0: return null;
                case 1: return attributes[0];
                default:
                    throw new InvalidOperationException(
                        string.Format("There are more than one custom attribute of type: {0} for property {1} of object {2}",
                        attributeType,
                        property.Name,
                        property.DeclaringType.ToString())
                        );
            }
        }

        /// <summary>
        /// Retrieves the custom attribute attached to the propertyInfo
        /// </summary>
        /// <typeparam name="T">The attributeType of the attribute to retrieve</typeparam>
        /// <param name="propertyInfo">The propertyInfo info to search for the attached attribute</param>
        /// <param name="inherit">Whether to retrieve the attributes up in the hierarchy</param>
        /// <returns>The attribute or null if not found</returns>
        public static T GetCustomAttribute<T>(PropertyInfo property, bool inherit)
        {
            return (T)GetCustomAttribute(property, typeof(T), inherit);
        }

        /// <summary>
        /// Searches for the attributes that match the field of attribute field
        /// </summary>
        /// <param name="attributes">The existing array of attributes to find from</param>
        /// <param name="attributeType">The field of attribute to find</param>
        /// <returns>The array of attributes that matches the field of the attribute</returns>
        public static Object[] FindAttributes(Object[] attributes, Type attributeType)
        {
            bool allowMultiple = false;
            AttributeUsageAttribute attributeUsageAttribute = GetCustomAttribute<AttributeUsageAttribute>(attributeType, false);
            if (attributeUsageAttribute != null
                && attributeUsageAttribute.AllowMultiple == true) // There can be multiple attributes of the same attributeType
            {
                allowMultiple = true;
            }

            List<Object> matchedAttributes = new List<Object>();
            foreach (Object attribute in attributes)
            {
                if (attribute.GetType().Equals(attributeType))
                {
                    matchedAttributes.Add(attribute);

                    if (!allowMultiple)
                    {
                        break; // Only one
                    }
                }
            }

            Object[] returnedAttributes = new Object[matchedAttributes.Count];
            matchedAttributes.CopyTo(returnedAttributes);
            return returnedAttributes;
        }

        /// <summary>
        /// Searches for the attributes that match the field of attribute field
        /// </summary>
        /// <typeparam name="T">The field of attribute to find</typeparam>
        /// <param name="attributes">The existing array of attributes to find from</param>
        /// <returns>The array of attributes that match the field of the attribute</returns>
        public static T[] FindAttributes<T>(Object[] attributes)
        {
            Object[] objects = FindAttributes(attributes, typeof(T));
            T[] returnedAttributes = new T[objects.Length];
            objects.CopyTo(returnedAttributes, 0);

            return returnedAttributes;
        }

        /// <summary>
        /// Searches for the attribute that matches the field of attribute field
        /// </summary>
        /// <typeparam name="T">The field of attribute to find</typeparam>
        /// <param name="attributes">The existing array of attributes to find from</param>
        /// <returns>The attribute that matches the field of the attribute</returns>
        public static T FindAttribute<T>(Object[] attributes)
        {
            T[] returnedAttributes = FindAttributes<T>(attributes);

            switch (returnedAttributes.Length)
            {
                case 0: return default(T);
                case 1: return returnedAttributes[0];
                default:
                    throw new InvalidOperationException(
                        string.Format("There are more than one custom attribute of type: {0}", typeof(T).FullName)
                        );
            }
        }
    }
}
